package grade.calculate.config;

import grade.calculate.constant.Num;
import grade.calculate.constant.Scale;
import grade.calculate.event.Event;
import grade.calculate.listener.Listener;
import grade.calculate.listener.ReadListener;
import grade.calculate.log.Log;
import grade.calculate.pojo.Data;
import grade.calculate.read.AbstractRead;
import grade.calculate.read.Read;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * 启动程序和配置启动的参数
 *
 * @author fromdrowning
 * @date 2024/2/20 19:35
 */
@Getter
@NoArgsConstructor
public class Configure {

    /**
     * 需要读取Excel的文件名 文件要放在resources下并带上文件后缀
     */
    private String inputFileName;

    /**
     * 输出的表名 默认表名为学业表现分排名
     */
    private String outputSheetName;

    /**
     * 读取Excel文件的表 默认第一张表
     */
    private Integer sheetNum;

    /**
     * 计算结果保留的小数位数 默认保留两位小数
     */
    private Integer scale;

    /**
     * 事件监听器 默认初始化了一个{@link ReadListener}监听器
     */
    private Listener listener;

    /**
     * 本学期所有需要计算学业表现分的课程
     */
    private List<String> semesterCourseList;

    /**
     * 带参构造方法
     *
     * @param configureBuilder {@link Configure}的构建器
     */
    private Configure(ConfigureBuilder configureBuilder) {
        // 获取ConfigureBuilder的所有字段
        Field[] builderFields = configureBuilder.getClass().getDeclaredFields();

        // 将ConfigureBuilder的所有字段赋值给Configure的字段
        try {
            for (Field builderField : builderFields) {
                Field configureField = this.getClass().getDeclaredField(builderField.getName());
                configureField.set(this, builderField.get(configureBuilder));
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建{@link Configure}对象
     *
     * @return {@link ConfigureBuilder} {@link Configure}的构建器
     */
    public static ConfigureBuilder builder() {
        return new ConfigureBuilder();
    }

    /**
     * {@link Configure}的构建器
     * <p>因为要进行一些定制化的处理</p>
     * <p>没有用Lombok的{@link lombok.Builder}注解</p>
     *
     * @author fromdrowning
     * @date 2024/02/21
     */
    @NoArgsConstructor
    public static class ConfigureBuilder {

        /**
         * 需要读取Excel的文件名 文件要放在resources下并带上文件后缀
         */
        private String inputFileName;

        /**
         * 输出的表名 默认表名为学业表现分排名
         */
        private String outputSheetName = "学业表现分排名";

        /**
         * 读取Excel文件的表 默认第一张表
         */
        private Integer sheetNum = Num.getNum(Num.FIRST_SHEET);

        /**
         * 计算结果保留的小数位数 默认保留两位小数
         */
        private Integer scale = Scale.getScale(Scale.TWO);

        /**
         * 事件监听器 默认初始化了一个{@link ReadListener}监听器
         */
        private Listener listener = new ReadListener();

        /**
         * 本学期所有需要计算学业表现分的课程
         */
        private List<String> semesterCourseList;

        /**
         * 程序启动方法 使用默认{@link Read}方法
         */
        public void start() {
            checkFields();

            // 将配置好的参数封装到Data中 获取数据统一在Data中获取
            Data data = new Data(new Configure(this));

            // 实例化默认Read方法并运行
            try {
                new Read(data).run();
            } catch (IOException e) {
                throw new RuntimeException("无法读取到文件 可能是以下错误\n" +
                        "设置读取的文件名与源文件名不一致 读取的文件没有放在resources包下 程序在含有中文的路径下启动了");
            }

            System.out.println("运行完成 结束程序");
        }

        /**
         * 程序启动方法 使用自定义读取方法
         *
         * @param read 自定义读取方法 必须是{@link AbstractRead}的实现类
         */
        public void start(AbstractRead read) {
            checkFields();

            // 将配置好的参数封装到Data中 获取数据统一在Data中获取
            Data data = new Data(new Configure(this));

            try {
                // 获取到自定义读取方法传入参数为Data的构造方法 通过改构造方法实例化对象并运行
                read.getClass()
                        .getDeclaredConstructor(Data.class)
                        .newInstance(data)
                        .run();
            } catch (NoSuchMethodException | IOException e) {
                if (e instanceof NoSuchMethodException) {
                    throw new RuntimeException("传入的Read方法中没有一个传入参数为Data的构造方法");
                } else {
                    throw new RuntimeException("无法读取到文件 可能是以下错误\n" +
                            "设置读取的文件名与源文件名不一致 读取的文件没有放在resources包下 程序在含有中文的路径下启动了");
                }
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }

            System.out.println("运行完成 结束程序");
        }

        /**
         * 判断是否有空字段
         */
        private void checkFields() {
            // 获取ConfigureBuilder的所有字段
            Field[] fields = this.getClass().getDeclaredFields();

            // 遍历所有字段 判断是否有null字段
            try {
                for (Field field : fields) {
                    // 获取private字段的读取权限
                    field.setAccessible(true);
                    // 判断该字段是否为null 为null抛异常
                    Optional.ofNullable(field.get(this))
                            .orElseThrow(() -> new RuntimeException(field.getName() + "不能为空"));
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        /**
         * 设置需要读取Excel的文件名 文件要放在resources下并带上文件后缀
         *
         * @param inputFileName 需要读取的文件名
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setInputFileName(String inputFileName) {
            this.inputFileName = inputFileName;
            return this;
        }

        /**
         * 设置输出的表名 默认表名为学业表现分排名
         *
         * @param outputSheetName 输出的表名
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setOutputSheetName(String outputSheetName) {
            this.outputSheetName = outputSheetName;
            return this;
        }

        /**
         * 设置读取Excel文件的表 默认第一张表
         *
         * @param sheetNum 读取Excel文件的表
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setSheetNum(Num sheetNum) {
            this.sheetNum = Num.getNum(sheetNum);
            return this;
        }

        /**
         * 设置计算结果保留的小数位数 默认保留两位小数
         *
         * @param scale 计算结果保留的小数位数
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setScale(Scale scale) {
            this.scale = Scale.getScale(scale);
            return this;
        }

        /**
         * 设置事件监听器 默认初始化了一个{@link ReadListener}监听器
         * <p>需要在设置事件前设置</p>
         *
         * @param listener 事件监听器
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setListener(Listener listener) {
            this.listener = listener;
            return this;
        }

        /**
         * 设置读取方法执行完后触发的事件 需要在设置好事件监听器后设置
         * <p>如果设置有多个输出事件 注意输出的地址不能重复 否则后面输出的Excel文件会覆盖先输出的Excel文件</p>
         *
         * @param event 读取方法执行完后触发的事件
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setEventAfterRead(Event event) {
            // 设置事件前判断设置的监听器是否为null 为null抛异常
            Optional.ofNullable(listener)
                    .ifPresentOrElse(listener -> listener.addEventAfterRead(Log.printEventLog(event)), () -> {
                        throw new RuntimeException("添加事件需要在监听器后");
                    });

            return this;
        }

        /**
         * 设置本学期所有需要计算学业表现分的课程
         *
         * @param courses 本学期所有需要计算学业表现分的课程
         * @return {@link ConfigureBuilder} {@link Configure}的构建器
         */
        public ConfigureBuilder setSemesterCourseList(String... courses) {
            this.semesterCourseList = Arrays.asList(courses);
            return this;
        }
    }
}

